1 // This program is free software; you can redistribute it and/or
2 // modify it under the terms of the GNU General Public License
3 // as published by the Free Software Foundation; either version 2
4 // of the License, or (at your option) any later version.
5 //
6 // This program is distributed in the hope that it will be useful,
7 // but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // GNU General Public License for more details.
10 //
11 // You should have received a copy of the GNU General Public License
12 // along with this program; if not, write to the Free Software
13 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
14
15
16 using System;
17 using System.Diagnostics;
18 using Emgu.CV.Structure;
19 using Emgu.CV;
20
21 namespace FaceRecognition
22 {
23 /// <summary>
24 /// </summary>
25 [Serializable]
26 public class EigenObjectRecognizer
27 {
28 private Image<Gray, Single>[] _eigenImages;
29 private Image<Gray, Single> _avgImage;
30 private Matrix<float>[] _eigenValues;
31 private string[] _labels;
32 private double _eigenDistanceThreshold;
33
34 /// <summary>
35 /// Get the eigen vectors that form the eigen space
36 /// </summary>
37 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
38 public Image<Gray, Single>[] EigenImages
39 {
40 get { return _eigenImages; }
41 set { _eigenImages = value; }
42 }
43
44 /// <summary>
45 /// Get or set the labels for the corresponding training image
46 /// </summary>
47 public String[] Labels
48 {
49 get { return _labels; }
50 set { _labels = value; }
51 }
52
53 /// <summary>
54 /// Get or set the eigen distance threshold.
55 /// The smaller the number, the more likely an examined image will be treated as unrecognized object.
56 /// Set it to a huge number (e.g. 5000) and the recognizer will always treated the examined image as one of the known object.
57 /// </summary>
58 public double EigenDistanceThreshold
59 {
60 get { return _eigenDistanceThreshold; }
61 set { _eigenDistanceThreshold = value; }
62 }
63
64 /// <summary>
65 /// Get the average Image.
66 /// </summary>
67 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
68 public Image<Gray, Single> AverageImage
69 {
70 get { return _avgImage; }
71 set { _avgImage = value; }
72 }
73
74 /// <summary>
75 /// Get the eigen values of each of the training image
76 /// </summary>
77 /// <remarks>The set method is primary used for deserialization, do not attemps to set it unless you know what you are doing</remarks>
78 public Matrix<float>[] EigenValues
79 {
80 get { return _eigenValues; }
81 set { _eigenValues = value; }
82 }
83
84 private EigenObjectRecognizer()
85 {
86 }
87
88
89 /// <summary>
90 /// Create an object recognizer using the specific tranning data and parameters, it will always return the most similar object
91 /// </summary>
92 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
93 /// <param name="termCrit">The criteria for recognizer training</param>
94 public EigenObjectRecognizer(Image<Gray, Byte>[] images, ref MCvTermCriteria termCrit)
95 : this(images, GenerateLabels(images.Length), ref termCrit)
96 {
97 }
98
99 private static String[] GenerateLabels(int size)
100 {
101 String[] labels = new string[size];
102 for (int i = 0; i < size; i++)
103 labels[i] = i.ToString();
104 return labels;
105 }
106
107 /// <summary>
108 /// Create an object recognizer using the specific tranning data and parameters, it will always return the most similar object
109 /// </summary>
110 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
111 /// <param name="labels">The labels corresponding to the images</param>
112 /// <param name="termCrit">The criteria for recognizer training</param>
113 public EigenObjectRecognizer(Image<Gray, Byte>[] images, String[] labels, ref MCvTermCriteria termCrit)
114 : this(images, labels, 0, ref termCrit)
115 {
116 }
117
118 /// <summary>
119 /// Create an object recognizer using the specific tranning data and parameters
120 /// </summary>
121 /// <param name="images">The images used for training, each of them should be the same size. It's recommended the images are histogram normalized</param>
122 /// <param name="labels">The labels corresponding to the images</param>
123 /// <param name="eigenDistanceThreshold">
124 /// The eigen distance threshold, (0, ~1000].
125 /// The smaller the number, the more likely an examined image will be treated as unrecognized object.
126 /// If the threshold is < 0, the recognizer will always treated the examined image as one of the known object.
127 /// </param>
128 /// <param name="termCrit">The criteria for recognizer training</param>
129 public EigenObjectRecognizer(Image<Gray, Byte>[] images, String[] labels, double eigenDistanceThreshold, ref MCvTermCriteria termCrit)
130 {
131 Debug.Assert(images.Length == labels.Length, "The number of images should equals the number of labels");
132 Debug.Assert(eigenDistanceThreshold >= 0.0, "Eigen-distance threshold should always >= 0.0");
133
134 CalcEigenObjects(images, ref termCrit, out _eigenImages, out _avgImage);
135
136 /*
137 _avgImage.SerializationCompressionRatio = 9;
138
139 foreach (Image<Gray, Single> img in _eigenImages)
140 //Set the compression ration to best compression. The serialized object can therefore save spaces
141 img.SerializationCompressionRatio = 9;
142 */
143
144 _eigenValues = Array.ConvertAll<Image<Gray, Byte>, Matrix<float>>(images,
145 delegate(Image<Gray, Byte> img)
146 {
147 return new Matrix<float>(EigenDecomposite(img, _eigenImages, _avgImage));
148 });
149
150 _labels = labels;
151
152 _eigenDistanceThreshold = eigenDistanceThreshold;
153 }
154
155 #region static methods
156 /// <summary>
157 /// Caculate the eigen images for the specific traning image
158 /// </summary>
159 /// <param name="trainingImages">The images used for training </param>
160 /// <param name="termCrit">The criteria for tranning</param>
161 /// <param name="eigenImages">The resulting eigen images</param>
162 /// <param name="avg">The resulting average image</param>
163 public static void CalcEigenObjects(Image<Gray, Byte>[] trainingImages, ref MCvTermCriteria termCrit, out Image<Gray, Single>[] eigenImages, out Image<Gray, Single> avg)
164 {
165 int width = trainingImages[0].Width;
166 int height = trainingImages[0].Height;
167
168 IntPtr[] inObjs = Array.ConvertAll<Image<Gray, Byte>, IntPtr>(trainingImages, delegate(Image<Gray, Byte> img) { return img.Ptr; });
169
170 if (termCrit.max_iter <= 0 || termCrit.max_iter > trainingImages.Length)
171 termCrit.max_iter = trainingImages.Length;
172
173 int maxEigenObjs = termCrit.max_iter;
174
175 #region initialize eigen images
176 eigenImages = new Image<Gray, float>[maxEigenObjs];
177 for (int i = 0; i < eigenImages.Length; i++)
178 eigenImages[i] = new Image<Gray, float>(width, height);
179 IntPtr[] eigObjs = Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; });
180 #endregion
181
182 avg = new Image<Gray, Single>(width, height);
183
184 CvInvoke.cvCalcEigenObjects(
185 inObjs,
186 ref termCrit,
187 eigObjs,
188 null,
189 avg.Ptr);
190 }
191
192 /// <summary>
193 /// Decompose the image as eigen values, using the specific eigen vectors
194 /// </summary>
195 /// <param name="src">The image to be decomposed</param>
196 /// <param name="eigenImages">The eigen images</param>
197 /// <param name="avg">The average images</param>
198 /// <returns>Eigen values of the decomposed image</returns>
199 public static float[] EigenDecomposite(Image<Gray, Byte> src, Image<Gray, Single>[] eigenImages, Image<Gray, Single> avg)
200 {
201 return CvInvoke.cvEigenDecomposite(
202 src.Ptr,
203 Array.ConvertAll<Image<Gray, Single>, IntPtr>(eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; }),
204 avg.Ptr);
205 }
206 #endregion
207
208 /// <summary>
209 /// Given the eigen value, reconstruct the projected image
210 /// </summary>
211 /// <param name="eigenValue">The eigen values</param>
212 /// <returns>The projected image</returns>
213 public Image<Gray, Byte> EigenProjection(float[] eigenValue)
214 {
215 Image<Gray, Byte> res = new Image<Gray, byte>(_avgImage.Width, _avgImage.Height);
216 CvInvoke.cvEigenProjection(
217 Array.ConvertAll<Image<Gray, Single>, IntPtr>(_eigenImages, delegate(Image<Gray, Single> img) { return img.Ptr; }),
218 eigenValue,
219 _avgImage.Ptr,
220 res.Ptr);
221 return res;
222 }
223
224 /// <summary>
225 /// Get the Euclidean eigen-distance between <paramref name="image"/> and every other image in the database
226 /// </summary>
227 /// <param name="image">The image to be compared from the training images</param>
228 /// <returns>An array of eigen distance from every image in the training images</returns>
229 public float[] GetEigenDistances(Image<Gray, Byte> image)
230 {
231 using (Matrix<float> eigenValue = new Matrix<float>(EigenDecomposite(image, _eigenImages, _avgImage)))
232 return Array.ConvertAll<Matrix<float>, float>(_eigenValues,
233 delegate(Matrix<float> eigenValueI)
234 {
235 return (float)CvInvoke.cvNorm(eigenValue.Ptr, eigenValueI.Ptr, Emgu.CV.CvEnum.NORM_TYPE.CV_L2, IntPtr.Zero);
236 });
237 }
238
239 /// <summary>
240 /// Given the <paramref name="image"/> to be examined, find in the database the most similar object, return the index and the eigen distance
241 /// </summary>
242 /// <param name="image">The image to be searched from the database</param>
243 /// <param name="index">The index of the most similar object</param>
244 /// <param name="eigenDistance">The eigen distance of the most similar object</param>
245 /// <param name="label">The label of the specific image</param>
246 public void FindMostSimilarObject(Image<Gray, Byte> image, out int index, out float eigenDistance, out String label)
247 {
248 float[] dist = GetEigenDistances(image);
249
250 index = 0;
251 eigenDistance = dist[0];
252 for (int i = 1; i < dist.Length; i++)
253 {
254 if (dist[i] < eigenDistance)
255 {
256 index = i;
257 eigenDistance = dist[i];
258 }
259 }
260 label = Labels[index];
261 }
262
263 /// <summary>
264 /// Try to recognize the image and return its label
265 /// </summary>
266 /// <param name="image">The image to be recognized</param>
267 /// <returns>
268 /// String.Empty, if not recognized;
269 /// Label of the corresponding image, otherwise
270 /// </returns>
271 public String Recognize(Image<Gray, Byte> image)
272 {
273 int index;
274 float eigenDistance;
275 String label;
276 FindMostSimilarObject(image, out index, out eigenDistance, out label);
277
278 return (_eigenDistanceThreshold <= 0 || eigenDistance < _eigenDistanceThreshold ) ? _labels[index] : String.Empty;
279 }
280 }
281 }